/*
 * FtpCode.cpp
 *  @see http://www.ietf.org/rfc/rfc959.txt   page 53
 *  Created on: 2012-12-25
 *      Author: yangentao@gmail.com
 */

#include <WinSock2.h>
#include <sstream>
#include <ctype.h>
#include "Packet.h"
#include "../yet/Set.h"
#include "../yet/baseutil.h"
#include "../yet/net/NetUtil.h"
using namespace std;

namespace ftp {
using namespace yet;

string ftpAddr(string host, u_short port) {
	InetAddr addr(host, port);
	u_long ip = addr.getIpLong();
	ip = ntohl(ip);
	u_char b1 = u_char(ip >> 24);
	u_char b2 = u_char(ip >> 16);
	u_char b3 = u_char(ip >> 8);
	u_char b4 = u_char(ip);
	u_char p1 = u_char(port >> 8);
	u_char p2 = u_char(port);
	char buf[64];
	sprintf_s(buf, 64, "%u,%u,%u,%u,%u,%u", b1, b2, b3, b4, p1, p2);
	return string(buf);
}

string getIpByFtpAddr(string ftpaddr) {
	string s;
	int n = 0;
	for (size_t i = 0; i < ftpaddr.size(); ++i) {
		char ch = ftpaddr[i];
		if (isdigit(ch)) {
			s.append(&ch, 1);
		}
		if (ispunct(ch)) {
			if (n >= 3) {
				return s;
			}
			s.append(".", 1);
			++n;
		}
	}
	return "";
}
u_short getPortByFtpAddr(string ftpaddr) {
	u_int b1 = 0;
	u_int b2 = 0;
	vector<string> v = split(ftpaddr, ',');
	if (v.size() == 6) {
		b1 = atoi(v[4].c_str());
		b2 = atoi(v[5].c_str());
		return b1 * 256 + b2;
	}
	return 0;
}

const static char ftpnames[FTP_CMD_COUNT][5] = { "NONE", "CONN", "USER", "PASS", "ACCT", "CWD", "CDUP", "SMNT", "REIN", "QUIT",
		"PORT", "PASV", "MODE", "TYPE", "STRU", "ALLO", "REST", "STOR", "STOU", "RETR", "LIST", "NLST", "APPE", "RNFR",
		"RNTO", "DELE", "RMD", "MKD", "PWD", "ABOR", "SYST", "STAT", "HELP", "SITE", "NOOP",  "FEAT"};

string ftpCmdName(int ftpCmd) {
	assert(ftpCmd >=0 && ftpCmd <FTP_CMD_COUNT);
	return ftpnames[ftpCmd];
}

string trimCrlf(string& s) {
	if (s.empty()) {
		return s;
	}
	int n = 0;
	if (s[s.size() - 1] == 10 || s[s.size() - 1] == 13) {
		++n;
	}
	if (s.size() >= 2 && n > 0) {
		if (s[s.size() - 2] == 10 || s[s.size() - 2] == 13) {
			++n;
		}
	}
	if (n > 0) {
		return s.substr(0, s.size() - n);
	}
	return s;
}

static Set<int> getSetA() {
	Set<int> s;
	s.put(FTP_ABOR);
	s.put(FTP_ALLO);
	s.put(FTP_DELE);
	s.put(FTP_CWD);
	s.put(FTP_CDUP);
	s.put(FTP_SMNT);
	s.put(FTP_HELP);
	s.put(FTP_MODE);
	s.put(FTP_NOOP);
	s.put(FTP_PASV);
	s.put(FTP_QUIT);
	s.put(FTP_SITE);
	s.put(FTP_PORT);
	s.put(FTP_SYST);
	s.put(FTP_STAT);
	s.put(FTP_RMD);
	s.put(FTP_MKD);
	s.put(FTP_PWD);
	s.put(FTP_STRU);
	s.put(FTP_TYPE);
//ext
	s.put(FTP_FEAT);
	return s;
}
static Set<int> getSetB() {
	Set<int> s;
	s.put(FTP_APPE);
	s.put(FTP_LIST);
	s.put(FTP_NLST);
	s.put(FTP_REIN);
	s.put(FTP_RETR);
	s.put(FTP_STOR);
	s.put(FTP_STOU);
	return s;
}

Set<int> setA = getSetA();
Set<int> setB = getSetB();

bool isFtpCmdTypeA(int cmd) {
	return setA.has(cmd);
}
bool isFtpCmdTypeB(int cmd) {
	return setB.has(cmd);
}

bool inFtpRange(int code, int n) {
	return InRange11(code, n * 100, (n + 1) * 100 - 1);
}
bool inFtpRange2(int code, int n, int m) {
	return inFtpRange(code, n) || inFtpRange(code, m);
}

int testFtpCode(int cmd, int code) {
	assert(code > 0);
	assert(cmd > 0);
	if (inFtpRange2(code, 4, 5)) {
		return FTP_FAILED;
	}
	if (isFtpCmdTypeA(cmd)) {
		if (inFtpRange2(code, 1, 3)) {
			return FTP_ERROR;
		}
		if (inFtpRange(code, 2)) {
			return FTP_OK;
		}
	} else if (isFtpCmdTypeB(cmd)) {
		if (inFtpRange(code, 1)) {
			return FTP_WAIT;
		}
		if (inFtpRange(code, 2)) {
			return FTP_OK;
		}
		if (inFtpRange(code, 3)) {
			return FTP_ERROR;
		}
	} else if (cmd == FTP_RNFR || cmd == FTP_REST) {
		if (inFtpRange2(code, 1, 2)) {
			return FTP_ERROR;
		}
		if (inFtpRange(code, 3)) {
			return FTP_NEXT;
		}
	} else if (cmd == FTP_RNTO) {
		if (inFtpRange2(code, 1, 3)) {
			return FTP_ERROR;
		}
		if (inFtpRange(code, 2)) {
			return FTP_OK;
		}
	} else if (cmd == FTP_USER || cmd == FTP_PASS) {
		if (inFtpRange(code, 1)) {
			return FTP_ERROR;
		}
		if (inFtpRange(code, 2)) {
			return FTP_OK;
		}
		if (inFtpRange(code, 3)) {
			return FTP_NEXT;
		}
	} else if (cmd == FTP_ACCT) {
		if (inFtpRange2(code, 1, 3)) {
			return FTP_ERROR;
		}
		if (inFtpRange(code, 2)) {
			return FTP_OK;
		}
	}
//============default===========
	//FTP_CONNECT
	if (inFtpRange(code, 1)) {
		return FTP_WAIT;
	}
	if (inFtpRange(code, 2)) {
		return FTP_OK;
	}
	if (inFtpRange(code, 3)) {
		return FTP_NEXT;
	}
	return FTP_WAIT;
}

Packet::Packet() {
	cmd = FTP_NONE;
}
Packet::~Packet() {
}

string Packet::getRequest() {
	stringstream ss;
	ss << cmdName();
	if (!requestArg1.empty()) {
		ss << " " << requestArg1;
		if (!requestArg2.empty()) {
			ss << " " << requestArg2;
		}
	}
	ss << endl;
	return ss.str();
}
void Packet::CONNECT() {
	cmd = FTP_CONNECT;
	requestArg1 = "";
	requestArg2 = "";
}
void Packet::USER(string user) {
	cmd = FTP_USER;
	requestArg1 = user;
	requestArg2 = "";
}
void Packet::PASS(string pass) {
	cmd = FTP_PASS;
	requestArg1 = pass;
	requestArg2 = "";
}
void Packet::ACCT(string acct) {
	cmd = FTP_ACCT;
	requestArg1 = acct;
	requestArg2 = "";
}
void Packet::CWD(string pathname) {
	cmd = FTP_CWD;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::CDUP() {
	cmd = FTP_CDUP;
	requestArg1 = "";
	requestArg2 = "";
}
void Packet::SMNT(string pathname) {
	cmd = FTP_SMNT;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::QUIT() {
	cmd = FTP_QUIT;
	requestArg1 = "";
	requestArg2 = "";
}
void Packet::REIN() {
	cmd = FTP_REIN;
	requestArg1 = "";
	requestArg2 = "";
}
//TODO port
void Packet::PORT(string host, short port) {
	cmd = FTP_PORT;
	requestArg1 = host;
	stringstream ss;
	ss << port;
	requestArg2 = ss.str();
}
void Packet::PASV() {
	cmd = FTP_PASV;
	requestArg1 = "";
	requestArg2 = "";
}
void Packet::TYPE(string type) {
	cmd = FTP_TYPE;
	requestArg1 = type;
	requestArg2 = "";
}
void Packet::STRU(string stru) {
	cmd = FTP_STRU;
	requestArg1 = stru;
	requestArg2 = "";
}
void Packet::MODE(string mode) {
	cmd = FTP_MODE;
	requestArg1 = mode;
	requestArg2 = "";
}
void Packet::RETR(string pathname) {
	cmd = FTP_RETR;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::STOR(string pathname) {
	cmd = FTP_STOR;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::STOU() {
	cmd = FTP_STOU;
	requestArg1 = "";
	requestArg2 = "";
}
void Packet::APPE(string pathname) {
	cmd = FTP_APPE;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::ALLO(string n1, string n2) {
	cmd = FTP_ALLO;
	requestArg1 = n1;
	requestArg2 = n2;
}
void Packet::REST(string maker) {
	cmd = FTP_REST;
	requestArg1 = maker;
	requestArg2 = "";
}
void Packet::RNFR(string pathname) {
	cmd = FTP_RNFR;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::RNTO(string pathname) {
	cmd = FTP_RNTO;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::ABOR() {
	cmd = FTP_ABOR;
	requestArg1 = "";
	requestArg2 = "";
}
void Packet::DELE(string pathname) {
	cmd = FTP_DELE;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::RMD(string pathname) {
	cmd = FTP_RMD;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::MKD(string pathname) {
	cmd = FTP_MKD;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::PWD() {
	cmd = FTP_PWD;
	requestArg1 = "";
	requestArg2 = "";
}
void Packet::LIST(string pathname) {
	cmd = FTP_LIST;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::NLST(string pathname) {
	cmd = FTP_NLST;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::SITE(string s) {
	cmd = FTP_SITE;
	requestArg1 = s;
	requestArg2 = "";
}
void Packet::SYST() {
	cmd = FTP_SYST;
	requestArg1 = "";
	requestArg2 = "";
}
void Packet::STAT(string pathname) {
	cmd = FTP_STAT;
	requestArg1 = pathname;
	requestArg2 = "";
}
void Packet::HELP(string s) {
	cmd = FTP_HELP;
	requestArg1 = s;
	requestArg2 = "";
}
void Packet::NOOP() {
	cmd = FTP_NOOP;
	requestArg1 = "";
	requestArg2 = "";
}

void Packet::FEAT() {
	cmd = FTP_FEAT;
	requestArg1 = "";
	requestArg2 = "";
}

bool Packet::isEmptyCmd() {
	return cmd == FTP_NONE;
}

void Packet::appendResponse(string response) {
	responseText = responseText + response;
	responseParser.parse(responseText);
}
string Packet::cmdName() {
	return ftpCmdName(cmd);
}
void Packet::clearAll() {
	cmd = FTP_NONE;
	requestArg1 = "";
	requestArg2 = "";
	responseText.clear();
	responseParser.clear();
}

bool Packet::isOk() {
	for (int i = responseParser.size() - 1; i >= 0; --i) {
		if (FTP_OK == testFtpCode(cmd, responseParser.getCode(i))) {
			return true;
		}
	}
	return false;
}
bool Packet::isNext() {
	for (int i = responseParser.size() - 1; i >= 0; --i) {
		if (FTP_NEXT == testFtpCode(cmd, responseParser.getCode(i))) {
			return true;
		}
	}
	return false;
}
bool Packet::isFailed() {
	for (int i = responseParser.size() - 1; i >= 0; --i) {
		if (FTP_FAILED == testFtpCode(cmd, responseParser.getCode(i))) {
			return true;
		}
	}
	return false;
}
bool Packet::isError() {
	for (int i = responseParser.size() - 1; i >= 0; --i) {
		if (FTP_ERROR == testFtpCode(cmd, responseParser.getCode(i))) {
			return true;
		}
	}
	return false;
}
bool Packet::isWait() {
	for (int i = responseParser.size() - 1; i >= 0; --i) {
		if (FTP_WAIT == testFtpCode(cmd, responseParser.getCode(i))) {
			return true;
		}
	}
	return false;
}

bool Packet::hasResponse() {
	return !responseParser.empty();
}
int Packet::responseCount() {
	return responseParser.size();
}
int Packet::getCode(int index) {
	return responseParser.getCode(index);
}
vector<string> Packet::getLines(int index) {
	return responseParser.getLines(index);
}
int Packet::firstCode() {
	assert(hasResponse());
	return getCode(0);
}
int Packet::lastCode() {
	assert(hasResponse());
	return getCode(responseCount() - 1);
}
string Packet::firstLine() {
	assert(hasResponse());
	return getLines(0)[0];
}
string Packet::lastLine() {
	assert(hasResponse());
	return getLines(responseCount() - 1)[0];
}

//================================cmd=====================

} /* namespace yet */
